#include "xlcomm/net/tcp_client.h"

#include <thread>

#include "xlcomm/base/date_time.h"
#include "xlcomm/net/detail/connector.h"
#include "xlcomm/net/detail/sockets_ops.h"

namespace xlcomm {
namespace net {

void DefaultClientConnection(const TcpConnectionPtr& conn) {
  LOG_INFO << __FUNCTION__ << " " << conn->name() << " is "
           << (conn->IsConnected() ? "UP" : "DOWN")
           << ", local:" << conn->local_addr().ToIpPort();
}

void DefaultClientMessage(const TcpConnectionPtr& conn, Buffer* buf,
                          Timestamp) {
  LOG_INFO << __FUNCTION__ << " " << conn->name() << " only retrieve all";
  buf->HasReadAll();
}

namespace detail {

void RemoveConnection(EventLoop* loop, const TcpConnectionPtr& conn) {
  loop->QueueInLoop(std::bind(&TcpConnection::ConnectDestroyed, conn));
}

void RemoveConnector(const ConnectorPtr& connector) {
  // connector->
}

}  // namespace detail

TcpClient::TcpClient(EventLoop* loop, const InetAddress& server_addr,
                     const std::string& name, bool retry)
    : loop_(loop),
      name_(name),
      conn_id_(1),
      connector_(new Connector(loop, server_addr)),
      will_connect_(true),
      retry_(retry),
      connect_callback_(DefaultClientConnection),
      disconnect_callback_(DefaultClientConnection),
      message_callback_(DefaultClientMessage) {
  XLCOMM_ASSERT(loop_ != nullptr);
  LOG_INFO << "TcpClient [" << name_ << "] create";
  connector_->set_new_connection_callback(
      std::bind(&TcpClient::NewConnection, this, _1));
}

TcpClient::~TcpClient() {
  LOG_INFO << "TcpClient [" << name_ << "] dtor";
  TcpConnectionPtr conn;
  bool unique = false;
  {
    std::lock_guard<std::mutex> guard(mutex_);
    unique = connection_.unique();
    conn = connection_;
  }
  if (conn) {
    XLCOMM_ASSERT(loop_ == conn->loop());
    // FIXME: not 100% safe, if we are in different thread
    CloseCallback cb = std::bind(&detail::RemoveConnection, loop_, _1);
    loop_->RunInLoop(std::bind(&TcpConnection::set_close_callback, conn, cb));
    if (unique) {
      conn->ForceClose();
    }
  } else {
    connector_->Stop();
    // FIXME: HACK
    loop_->RunAfter(1, std::bind(&detail::RemoveConnector, connector_));
  }
}

void TcpClient::Connect() {
  LOG_INFO << "TcpClient [" << name_ << "] connecting to "
           << connector_->server_addr().ToIpPort() << "...";
  will_connect_ = true;
  connector_->Start();
}

void TcpClient::Disconnect() {
  LOG_INFO << "TcpClient [" << name_ << "] disconnect from "
           << connector_->server_addr().ToIpPort();
  will_connect_ = false;
  {
    std::lock_guard<std::mutex> guard(mutex_);
    if (connection_) connection_->Shutdown();
  }
}

void TcpClient::Stop() {
  LOG_INFO << "TcpClient [" << name_ << "] stop connect to "
           << connector_->server_addr().ToIpPort();
  will_connect_ = false;
  connector_->Stop();
}

void TcpClient::NewConnection(int sockfd) {
  XLCOMM_ASSERT(loop_->IsInLoopThread());
  InetAddress local_addr = InetAddress(sockets::GetLocalAddr(sockfd));
  InetAddress peer_addr = InetAddress(sockets::GetPeerAddr(sockfd));
  char buff[64];
  snprintf(buff, sizeof(buff), "#%lu-%s", conn_id_,
           peer_addr.ToIpPort().c_str());
  ++conn_id_;
  std::string conn_name = "'" + name_ + buff + "'";
  LOG_INFO << "TcpClient [" << name_ << "] has connected " << conn_name
           << ", local:" << local_addr.ToIpPort() << " conn_fd=" << sockfd;

  // FIXME: poll with zero timeout to double confirm the new connection
  TcpConnectionPtr conn = std::make_shared<TcpConnection>(
      loop_, conn_name, sockfd, local_addr, connector_->server_addr());
  conn->set_connect_callback(connect_callback_);
  conn->set_disconnect_callback(disconnect_callback_);
  conn->set_message_callback(message_callback_);
  conn->set_write_complete_callback(write_complete_callback_);
  conn->set_close_callback(std::bind(&TcpClient::RemoveConnection, this,
                                     _1));  // FIXME: unsafe
  {
    std::lock_guard<std::mutex> guard(mutex_);
    connection_ = conn;
  }
  conn->ConnectEstablished();
}

void TcpClient::RemoveConnection(const TcpConnectionPtr& conn) {
  XLCOMM_ASSERT(loop_->IsInLoopThread());
  LOG_INFO << "closed, TcpClient [" << name_ << "] remove connection "
           << conn->name();
  {
    std::lock_guard<std::mutex> guard(mutex_);
    XLCOMM_ASSERT(conn == connection_);
    connection_.reset();
  }
  loop_->QueueInLoop(std::bind(&TcpConnection::ConnectDestroyed, conn));
  if (retry_ && will_connect_) {
    // FIXME：等待一段随机时间(0.5~2s)再重试
    LOG_INFO << "TcpClient [" << name_ << "] reconnecting to "
             << connector_->server_addr().ToIpPort() << "...";
    connector_->Restart();
  }
}

}  // namespace net
}  // namespace xlcomm